Subject: Re: Help - Boopsi
From: [email-masked] (Doug Walker)
Date: Mon, 16 May 1994 16:02:35 GMT
Newsgroups: comp.sys.amiga.programmer

In article <[email-masked]>, [email-masked] (Dave Thomas) writes:
|> I was having exactly the same problem.  When I read this message, I got so
|> excited I just had to log-off and try it, and, lo & behold, it worked!
|> Now, the next question is, can anyone explain this to me?  Tell me if this
|> is what's happening:  whenever the dispatcher is called, the program is
|> saving variables, addresses, etc. on the stack, and since the dispatcher is
|> called quite often, the program runs out of stack space.  If this is the
|> case, and we're turning off stack checking, isn't the program going to
|> write past the end of the stack and corrupt some other part of memory?  Is
|> this safe?
|> 
|> Thanks for your help!

OK, here's a general discussion of stacks, stack checking, and
callback routines, since I've answered similar questions frequently
in the past couple of months.  Note that the discussion applies to
BOOPSI callbacks, software interrupts, input event handlers, shared
libraries, and lots of other callback-type situations.

As functions get called, they require space for local variables,
parameters, and compiler work space.  The amount needed varies
for different function, but it's always the same for the same
function: i.e. if you call function foo() four times, the amount
of stack needed is the same for each call.

When functions return, all the stack space they use is made
available for other functions to use.  After the return(),
the function isn't using any stack.  Therefore, it doesn't
matter how many times the dispatcher is called - it will never
use any more stack than the first time, assuming it doesn't
call itself recursively.

One more thing to note: each task in the Amiga OS has its own stack.
When BOOPSI calls your function, it calls your code from BOOPSI's task
context; this means that your function is using BOOPSI's stack during
that call.  This becomes important when you realize how stack checking
works.

How Stack Checking Works:

   When the program starts up, the startup code (c.o or equivalent) 
   makes a note of the lowest valid address in the stack.  The stack 
   pointer is set to a value in the high end of the stack, and gets 
   smaller as more data is added.

   At the beginning of each function, the compiler generates code to 
   compare the current stack pointer with the minimum stack location
   that the startup code stored.  If the difference is smaller than
   the amount of stack required for the current function, it aborts the
   program.  (If you're using stack EXTENSION instead of stack CHECKING,
   it actually allocates a new stack for you instead of aborting.)

Now, putting two and two together, we see that stack checking will only
work for one task's stack.  Why?  Because we're assuming that we know
the correct minimum stack value.  Since BOOPSI calls you on BOOPSI's 
stack, we don't know this correct value, and there is no way that stack
checking (or stack extension) can work!

You mention that your dispatcher stores values on the stack, so you're
worried that stack checking is necessary.  As long as you make sure
that your function doesn't allocate very much stack (a couple of 
hundred bytes at most) then you will be OK.  Your function will 
allocate stack for all AUTOMATIC variables and function parameters.
Static and extern variables are fine.

Speaking of static and extern variables, there is one other gotcha with
callbacks: the near data register.

Register A4 is usually set up by the compiler to point to your global
data.  The compiler generates code to access the global data relative
to register A4 instead of at an absolute memory address.  There are 
several advantages to this, including smaller code, faster code, and
reentrant code (code that can be part of several processes at the
same time.)

When BOOPSI calls your callback function, it does not set up register
A4 for you.  You have to make sure it is set up if you want to use it.
There are many solutions to this problem, including the following:

   1. If you don't care about using near data, compile with DATA=FAR.
      This will eliminate the use of register A4 for global data.
      
   2. If your program was not linked with cres.o, declare your BOOPSI
      callback with the __saveds keyword.  This will force the compiler
      to generate code to load A4 at the start of the function.  This
      will not work from cres.o programs.

   3. From cres.o programs, pass the correct A4 value as your UserData
      parameter.  Get its value with the "getreg()" function (defined 
      in <dos.h>):

         long UserData = getreg(REG_A4);

      once you receive it in your callback, put it with putreg:

         putreg(REG_A4, UserData);

Note that "string literals" are also global data, so you may need 
global data in places that you don't think you do...
-- 
  *****                    / [email-masked]
 *|_o_o|\\     Doug Walker<  BIX, Portal: djwalker
 *|. o.| ||                \ CompuServe: 71165,2274
  | o  |//     
  ====== 
Any opinions expressed are mine, not those of SAS Institute, Inc.